﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WcfContrib.Client.Behaviors;
using WcfContrib.Client.Description;
using WcfContrib.Client;
using WcfContrib.Utils;
using System.Reflection;

namespace WcfContrib.Extensions.Caching
{
    /// <summary>
    /// Represents output caching attribute for client channels, service or operation contracts
    /// </summary>
    public class OutputCacheBehaviorAttribute : Attribute, IClientEndpointBehavior, IClientContractBehavior, IClientOperationBehavior
    {
        #region Fields
        const bool DefaultCompareEnumerableItems = true;
        const OutputCachePolicy DefaultPolicy = OutputCachePolicy.Indefinite;
        const long DefaultMillisecondsInterval = 0;
        #endregion

        /// <summary>
        /// Gets or sets a value indicating whether to ignore the default return value.
        /// </summary>
        /// <value><c>true</c> if to ignore the default return value; otherwise, <c>false</c>.</value>
        public bool IgnoreDefaultValue { get; set; }
        /// <summary>
        /// Gets a value indicating whether to compare IEnumerable inline items.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if to compare IEnumerable inline items; otherwise, <c>false</c>.
        /// </value>
        public bool CompareEnumerableItems { get; set; }

        OutputCachePolicy policy;
        TimeSpan interval;
        Dictionary<int, IEqualityComparer<object>> comparers;

        #region Instancing
        /// <summary>
        /// Initializes a new instance of the <see cref="OutputCacheBehaviorAttribute"/> class.
        /// </summary>
        /// <param name="policy">The cache policy.</param>
        /// <param name="millisecondsInterval">The time interval in milliseconds.</param>
        /// <param name="comparerTypes">The index-based comparers used to distinguish between pazrameter instances.</param>
        public OutputCacheBehaviorAttribute(OutputCachePolicy policy, long millisecondsInterval, params Type[] comparerTypes)
        {
            CompareEnumerableItems = DefaultCompareEnumerableItems;
            interval = TimeSpan.FromMilliseconds(millisecondsInterval);
            this.policy = policy;

            if (comparerTypes.Length > 0)
            {
                comparers = new Dictionary<int, IEqualityComparer<object>>();
                for (int i = 0; i < comparerTypes.Length; i++)
                {
                    if (comparerTypes[i] != null)
                    {
                        var comparer = (IEqualityComparer<object>)Activator.CreateInstance(comparerTypes[i]);
                        comparers.Add(i, comparer);
                    }
                }   
            }
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="OutputCacheBehaviorAttribute"/> class.
        /// </summary>
        /// <param name="comparerTypes">The index-based comparers used to distinguish between pazrameter instances.</param>
        public OutputCacheBehaviorAttribute(params Type[] comparerTypes)
            : this(DefaultPolicy, DefaultMillisecondsInterval, comparerTypes) { }
        #endregion

        #region IClientEndpointBehavior Members

        /// <summary>
        /// Applies the client behavior.
        /// </summary>
        /// <param name="clientChannel">The client channel.</param>
        /// <param name="endpointDescription">The endpoint description.</param>
        public void ApplyClientBehavior(ClientChannel clientChannel, ClientEndpointDescription endpointDescription)
        {
            endpointDescription.Contract.Behaviors.AddUnique(this);
        }

        #endregion

        #region IClientContractBehavior Members

        /// <summary>
        /// Applies the client behavior.
        /// </summary>
        /// <param name="contractDescription">The contract description.</param>
        public void ApplyClientBehavior(ClientContractDescription contractDescription)
        {
            foreach (var operation in contractDescription.Operations)
                operation.Behaviors.AddUnique(this);
        }

        #endregion

        #region IClientOperationBehavior Members

        /// <summary>
        /// Applies the client behavior.
        /// </summary>
        /// <param name="operationDescription">The operation description.</param>
        public void ApplyClientBehavior(ClientOperationDescription operationDescription)
        {
            var outputCache = new OutputCacheBehavior(operationDescription.OperationName, operationDescription.AsyncPattern,
                policy, interval, CompareEnumerableItems, IgnoreDefaultValue,
                TypeHelper.CreateDefaultOfType(((MethodInfo)operationDescription.Method).ReturnType), comparers);

            operationDescription.Inspectors.Add(outputCache);
            operationDescription.Invokers.Add(outputCache);
        }

        #endregion
    }
}
